package org.cyz.tools.common.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import javafx.application.Platform;
import javafx.scene.control.TextArea;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.cyz.tools.common.bean.ResultBean;
import org.cyz.tools.core.assembly.log.Log;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;


/**
 * @program: RedisGUI
 * @description: redis util
 * @author: Chenyz
 * @create: 2020-08-11 08:53
 **/
public class RedisUtil {

    public static boolean isOpen = false;
    private static LinkedList<Integer> dbIndexList = new LinkedList<>();
    //标识当前连接信息
    protected static String connection;

    //等待可用连接的最大时间，单位毫秒，默认值为-1，表示永不超时。如果超过等待时间，则直接抛出JedisConnectionException；
    private static int MAX_WAIT = 3 * 1000;
    //超时时间
    private static int TIMEOUT = 3 * 1000;

    private static JedisPool jedisPool = null;
    private static Jedis jedis = null;

    private static String userName;
    private static String passWord;
    private static String serverUrl;
    private static int port = 6379;


    private static Jedis jedis_client;
    private static Jedis monitor_client;

    private static String protocol = "redis://";
    private static String ssl_protocol = "rediss://";

    private boolean useSSL;


    public RedisUtil() {
    }

    /**
     * @param serverUrl
     */
    public RedisUtil(String serverUrl) {
        Assert.isTrue(serverUrl.split(":").length <= 1);
        RedisUtil.serverUrl = serverUrl.split(":")[0];
    }

    public RedisUtil(String ip, int port) {
        Assert.notNull(ip, "serverUrl不能为空！");
        serverUrl = ip;
        RedisUtil.port = port;
    }

    public RedisUtil(String ip, int port, String userName) {
        Assert.isNull(ip, "serverUrl不能为空！");
        RedisUtil.port = port;
        serverUrl = ip;
    }

    protected static Jedis getClient() {
        if (null == jedis_client) {
            signleBuilder();
        }
        return jedis_client;
    }


    public RedisUtil setUseSSL(boolean useSSL) {
        this.useSSL = useSSL;
        return this;
    }

    /**
     * 组合地址
     *
     * @return
     */
    private String getAddress() {
        return this.useSSL ? ssl_protocol : protocol + serverUrl + ":" + port;
    }


    private synchronized static JedisPoolConfig initConfig() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            //最大连接数，如果赋值为-1，则表示不限制；如果pool已经分配了maxActive个jedis实例，则此时pool的状态为exhausted(耗尽)。
            config.setMaxTotal(5);
            //最大空闲数，控制一个pool最多有多少个状态为idle(空闲的)的jedis实例，默认值也是8。
            config.setMaxIdle(5);
            //最小空闲数
            config.setMinIdle(1);
            //是否在从池中取出连接前进行检验，如果检验失败，则从池中去除连接并尝试取出另一个
            config.setTestOnBorrow(true);
            //在return给pool时，是否提前进行validate操作
            config.setTestOnReturn(true);
            //在空闲时检查有效性，默认false
            config.setTestWhileIdle(true);
            //表示一个对象至少停留在idle状态的最短时间，然后才能被idle object evitor扫描并驱逐；
            //这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
            config.setMinEvictableIdleTimeMillis(3000);
            //表示idle object evitor两次扫描之间要sleep的毫秒数
            config.setTimeBetweenEvictionRunsMillis(6000);
            //表示idle object evitor每次扫描的最多的对象数
            config.setNumTestsPerEvictionRun(1000);
            //等待可用连接的最大时间，单位毫秒，默认值为-1，表示永不超时。如果超过等待时间，则直接抛出JedisConnectionException；
            config.setMaxWaitMillis(MAX_WAIT);
            return config;
        } catch (Exception e) {
            if (null != jedisPool) {
                jedisPool.close();
            }
            Log.info(RedisUtil.class.getSimpleName(),"jedis初始化失败了{%s}!", e.getMessage());
        }
        return null;
    }

    /**
     * 构建RedissonClient
     *
     * @return
     */
    private synchronized static void builder() {
        Assert.notEmpty(serverUrl, "ip地址不能为空!");
        JedisPoolConfig config = initConfig();
        if (null == config) {
            Log.error(RedisUtil.class.getSimpleName(),"连接池初始化失败了...");
        }
        connection = serverUrl + ":" + port;
        if (passWord != null && !"".equals(passWord)) {
            // redis 设置了密码
            jedisPool = new JedisPool(config, serverUrl, port, TIMEOUT, passWord);
        } else {
            // redis 未设置密码
            jedisPool = new JedisPool(config, serverUrl, port, TIMEOUT);
        }
    }


    private synchronized static void signleBuilder() {
        if (null != jedis_client && jedis_client.isConnected()) {
            return;
        }
        if (passWord != null && !"".equals(passWord)) {
            // redis 设置了密码
            jedis_client = new Jedis(serverUrl, port, TIMEOUT, true);
            monitor_client = new Jedis(serverUrl, port, TIMEOUT, true);
        } else {
            // redis 未设置密码
            jedis_client = new Jedis(serverUrl, port, TIMEOUT);
            monitor_client = new Jedis(serverUrl, port, TIMEOUT);
        }
        connection = serverUrl + ":" + port;
    }

    /**
     * 在多线程环境同步初始化
     */
    private static void poolInit() {
        if (jedisPool == null) {
//            builder();
            signleBuilder();
        }
    }

    /**
     * 同步获取Jedis实例
     *
     * @return Jedis
     */
    private synchronized static Jedis getJedis() {
//        if (jedisPool == null) {
//            poolInit();
//        }
//        Jedis jedis = null;
//        try {
//            if (jedisPool != null) {
//                jedis = jedisPool.getResource();
//            }
//        } catch (Exception e) {
//            releaseResource(jedis);
//            logger.error("同步获取Jedis实例失败" + e.getMessage(), e);
//            return null;
//        }
        if (null == jedis_client) {
            signleBuilder();
        }
        isOpen = true;
        return jedis_client;
    }

    /**
     * 释放jedis资源
     *
     * @param jedis
     */
    public static void releaseResource(Jedis jedis) {
        if (null != jedis) {
            jedis.close();
        }
    }


    /**
     * 获取db的数量
     *
     * @return
     */
    public static int getDBCount() throws Exception {
        int size = 16;
        String info = jedis_client.info("config get databases");
        if (null != info && info.split("\r\n").length > 1) {
            size = Integer.parseInt(info.split("\r\n")[1]);
        }
        return size;
    }

    /**
     * 获取db0的键个数
     *
     * @return
     */
    public static String getDBKeyCount(int index) {
        String currentInfo = null;
        switch (index) {
            case 0:
                getJedis();
                String info = jedis_client.info("all");
                for (String val : info.split("\r\n")) {
                    if ("db0".equalsIgnoreCase(val.split(":")[0]) && val.split(":").length >= 2) {
                        currentInfo = val.split(",")[0].split("=")[1];
                    }
                }
                break;
            default:
                currentInfo = 0 + "";
                break;
        }
        return currentInfo;
    }

    /**
     * @param dbIndex
     * @param index
     * @param match
     * @param count
     * @return
     */
    public static Set<String> getDBKeys(int dbIndex, String index, String match, int count) throws Exception {
        jedis_client.select(dbIndex);
        ScanResult scanResult = jedis_client.scan(index, new ScanParams().match(match).count(count));
        return scanResultToList(scanResult);
    }

    /**
     * 处理scan重复的问题
     *
     * @param scanResult
     * @return
     */
    protected static Set<String> scanResultToList(ScanResult scanResult) {
        if (CollectionUtil.isEmpty(scanResult.getResult())) {
            return null;
        }
        Set<String> sets = new HashSet<>();
        scanResult.getResult().stream().sorted().forEach(result -> {
            sets.add(result.toString());
        });
        return sets;
    }


    public static List<String> getDBKey(int dbIndex) {
        List<String> sets = new ArrayList<>();
        try {
            //切换db
            String state = jedis_client.select(dbIndex);
            if ("OK".equalsIgnoreCase(state)) {
                //切换库成功
//                sets= pub_jedis.keys("*");
                sets.addAll(jedis_client.keys("*"));
                Collections.sort(sets);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sets;
    }

    public synchronized void reBuilderRedis(String serverUrl, int port) {
        RedisUtil.serverUrl = serverUrl;
        RedisUtil.port = port;
        connection = serverUrl + ":" + port;
        signleBuilder();
    }

    public synchronized void reBuilderRedis(String serverUrl, int port, String userName, String passWord) {
        RedisUtil.serverUrl = serverUrl;
        RedisUtil.port = port;
        connection = serverUrl + ":" + port;
        signleBuilder();
    }

    /**
     * 这里组装三个内容 键类型 | 值 | 生存周期
     *
     * @param key
     * @return
     */
    public ResultBean getValue(String key) {
        String type = jedis_client.type(key);
        if (null == type) {
            Log.info(RedisUtil.class.getSimpleName(),"key:{%s} 不存在", key);
            return null;
        }
        ResultBean resultBean = new ResultBean(type);
        buildResultBean(type, key, resultBean);
        return resultBean;
    }

    /**
     * 构建需要的对象
     *
     * @param type
     * @param resultBean
     */
    private void buildResultBean(String type, String key, ResultBean resultBean) {
        resultBean.setKey(key);
        switch (type) {
            case "string":
                resultBean.setValue(jedis_client.get(key));
                resultBean.setLen(jedis_client.strlen(key));
                resultBean.setTtl(jedis_client.ttl(key));
                break;
            case "list":
                resultBean.setListValue(jedis_client.lrange(key, 0, -1));
                resultBean.setLen(jedis_client.llen(key));
//                resultBean.setTtl(pub_jedis.l);
                break;
            case "hash":
                resultBean.setHashValue(jedis_client.hgetAll(key));
                resultBean.setHkeys(jedis_client.hkeys(key));
                resultBean.setLen(jedis_client.hlen(key));
                break;
            case "set":
                resultBean.setSetValue(jedis_client.smembers(key));
                resultBean.setLen(jedis_client.scard(key));
                break;
            case "zset":
                resultBean.setzSetValue(jedis_client.zrange(key, 0, -1));
                resultBean.setLen(jedis_client.zcard(key));
                break;
            default:
                break;
        }
        releaseResource(jedis);
    }


    public void monitor(TextArea monitor) throws Exception {
        if (null == monitor_client) {
            monitor_client = getMonitorJedis();
        }
        monitor_client.monitor(new JedisMonitor() {
            @Override
            public void onCommand(String s) {
                Platform.runLater(() -> {
                    monitor.setText(monitor.getText() + LocalDateTime.now().toString().split("\\.")[0].replace("T", " ") + " - " + s + "\n");
                    //每次添加信息时刷新文本区域，将滚动条显示在末尾，即显示最新一次输入
                    monitor.positionCaret(monitor.getLength());
                });
            }
        });
    }


    public void cancelMonitor(TextArea monitor) {
        //斷開連接
        monitor_client.disconnect();
        monitor.clear();
    }


    private Jedis getMonitorJedis() {
        if (null == monitor_client) {
            signleBuilder();
        }
        isOpen = true;
        return jedis_client;
    }

    /**
     * 删除key
     *
     * @param key
     * @return
     */
    public boolean del(String key) {
        long count;
        try {
            count = jedis_client.del(key);
        } finally {
            releaseResource(jedis_client);
        }
        return count > 0;
    }

    /**
     * 重命名key
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public String rename(String oldKey, String newKey) {
        String result = null;
        try {
            result = jedis_client.rename(oldKey, newKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 关闭连接
     *
     * @return
     */
    public boolean closeClient() {
        try {
            if (null != jedis_client) {
                releaseResource(jedis_client);
            }
            Log.info(RedisUtil.class.getSimpleName(),"释放redis");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean closeMonitorClient() {
        try {
            if (null != monitor_client) {
                releaseResource(monitor_client);
            }
            Log.info(RedisUtil.class.getSimpleName(),"释放监控redis客户端");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 设置key的有效期
     *
     * @param sKey
     * @param result
     */
    public long setTTL(String sKey, int result) {
        try {
            return jedis_client.expire(sKey, result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 监控服务器资源情况
     */
    public static Map<String, String> getServerInfo() {
        Map<String, String> maps = new HashMap<>();
        String dbInfo = jedis_client.info("Keyspace");
        //db0:keys=4595417,expires=2621511,avg_ttl=174290372264773\n\r
        //db1:keys=1,expires=0,avg_ttl=0
        //db3:keys=1,expires=0,avg_ttl=0
        String[] str = dbInfo.split("\r\n");
        for (int i = 1; i < str.length; i++) {
            maps.put(str[i].split(":")[0], str[i].split(":")[1].split(",")[0].split("=")[1]);
        }
        return maps;
    }


    /*****************************************************************************************/
    /**************************************执行原生命令***************************************/
    /*****************************************************************************************/

    private List<String> execRedisCommand(Jedis jedis, String command, String... args) throws InvocationTargetException, IllegalAccessException {
        Protocol.Command cmd = Protocol.Command.valueOf(command.toUpperCase());
        Client client = jedis.getClient();
        Method method = MethodUtils.getMatchingMethod(Client.class, "sendCommand", Protocol.Command.class, String[].class);
        method.setAccessible(true);
        method.invoke(client, cmd, args);
        try {
            List<String> respList = new ArrayList<>();
            Object response = client.getOne();
            if (response instanceof List) {
                for (Object itemResp : ((List) response)) {
                    respList.add(new String((byte[]) itemResp));
                }
                return respList;
            } else {
                return Collections.singletonList(new String((byte[]) response));
            }

        } catch (JedisException e) {
            return Collections.singletonList(e.getMessage());
        }
    }

    public List<String> exec(String key) {
        jedis = getJedis();
        key = key.replace("\n", "");
        try {
            return execRedisCommand(jedis, key.split(" ")[0], key.substring(key.indexOf(" "), key.length()));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            releaseResource(jedis);
        }
        return null;
    }

    /**
     * 切换db
     *
     * @param value
     */
    public void selectDB(String value) {
        int dbIndex = Integer.valueOf(value.split(" ")[0].substring(2));
        if (!dbIndexList.contains(dbIndex)) {
            jedis_client.select(dbIndex);
            dbIndexList.add(dbIndex);
        } else if (dbIndexList.size() > 1) {
            dbIndexList.remove(0);
        }


    }

    public static void main(String[] args) {
        serverUrl = "127.0.0.1";
        port = 6379;
        signleBuilder();
        String type = jedis_client.type("AYKT:Sign:Record:CQSJEYB2QY:89760");
        System.out.println(type);
    }


    public boolean testConnectRedis(String serverUrl, String port) {
        Assert.notEmpty(serverUrl, "ip地址不能为空!");
        Assert.notEmpty(port, "端口不能为空!");
        JedisPoolConfig config = initConfig();
        // redis 未设置密码
        jedisPool = new JedisPool(config, serverUrl, Integer.parseInt(port), TIMEOUT);
        if (null == jedisPool) {
            return false;
        }
        jedis = jedisPool.getResource();
        if (null == jedis) {
            return false;
        }
        boolean flag = jedis.isConnected();
        closeClient();
        return flag;

    }
}
